namespace XTCast.Mp3
{
	using System;
	using System.IO;
	using XTCast.Mp3;
	using TerastarLib.Utils;
	
	public class MP3Frame	{

		internal const int VBR_FRAMES_FLAG  = 0x0001;
		internal const int VBR_BYTES_FLAG   = 0x0002;
		internal const int VBR_TOC_FLAG     = 0x0004;
		internal const int VBR_SCALE_FLAG   = 0x0008;

		public const int MillisLength = 26;
		public TimeSpan Length = TimeSpan.FromMilliseconds(MillisLength);

		internal const int maxFrameSize = 1731;

		private byte[] frameBuffer = new byte[maxFrameSize];
		private bool strictSync = false;
		private int header = 0, prevHeader = 0;
		private bool headerValid = false, frameValid = false, vbrFrame = false;
		private int size, headerBytesRead = 0;

		public MP3Frame() {
		}
		
		public virtual byte[] Frame
		{
			get
			{
				if (!frameValid) throw new System.InvalidOperationException();
				return frameBuffer;
			}
			
		}
		public virtual int MPEGLevel
		{
			get
			{
				if (!headerValid) throw new System.InvalidOperationException();
				return bit(header, 19)?1:2;
			}
			
		}


		public virtual int Channels
		{
			get
			{
				if (!headerValid) throw new System.InvalidOperationException();
				if (((header >> 6) & 0x03) == 0x03)
					return 1;
				else
					return 2;
			}
			
		}

		public virtual int MPEGLayer
		{
			get
			{
				if (!headerValid)
					throw new System.InvalidOperationException();
				int layer = (header >> 17) & 0x03;
				if (header == 0)
					return 0;
				else
					return 4 - layer;
			}
			
		}

		public virtual int BitRate
		{
			get
			{
				if (!headerValid)
					throw new System.InvalidOperationException();
				int value_Renamed = (header >> 12) & 0x0F;
				return bitrates[MPEGLevel - 1][MPEGLayer - 1][value_Renamed];
			}
		}
		
		public virtual int SampleRate
		{
			get
			{
				if (!headerValid)
					throw new System.InvalidOperationException();
				int rate;
				switch ((header >> 10) & 0x03)
				{
					case 0: 
						rate = 44100; break;
					
					case 1: 
						rate = 48000; break;
					
					case 2: 
						rate = 32000; break;
					
					default: 
						return 0; // illegal
					
				}
				if (MPEGLevel > 1)
					rate >>= 1;
				return rate;
			}
		}

		public virtual bool Padding
		{
			get
			{
				if (!headerValid)
					throw new System.InvalidOperationException();
				return bit(header, 9);
			}
		}

		public virtual bool Protection
		{
			get
			{
				if (!headerValid)
					throw new System.InvalidOperationException();
				return !bit(header, 16);
			}
		}
		
		public virtual int Size
		{
			get
			{
				if (size == 0) size = CalcFrameSize();
				return size;
			}
		}
				
		public virtual void SetStrictSync()
		{
			strictSync = true;
		}
	
		public virtual bool ReadFrame(Stream ins)
		{ 
			if (ReadHeader(ins)) 
			{
				frameBuffer[0] = (byte) ((header >> 24) & 0xFF);
				frameBuffer[1] = (byte) ((header >> 16) & 0xFF);
				frameBuffer[2] = (byte) ((header >> 8) & 0xFF);
				frameBuffer[3] = (byte) (header & 0xFF);
			}
			else return false;
			int readBytes = Size - 4;
			frameValid = ins.Read(frameBuffer, 4, readBytes) == readBytes;
			vbrFrame = isVbrFrame();
			return frameValid;
		}

		public virtual bool ReadHeader(Stream ins) 
		{
			headerValid = false;
			size = 0;
			while (true) {
				try {
					int i=ins.ReadByte();
					if (i<0)
						return false;
					headerBytesRead += 1;
					header = (header << 8) | (i & 0xFF);
				} 
				catch (System.IO.EndOfStreamException) 
				{
					return false;
				}
				if (headerBytesRead>=4 && isSyncMark(header)) {
					headerBytesRead=0;
					headerValid = true;
					if (headerBytesRead>4) Log.Verbose("MP3 Frame Reader Lost Sync, Skipped " + (headerBytesRead-4) + " byte(s)");
					return true;
				}
			}
		}

		private int CalcFrameSize()
		{
			if (!headerValid)
				throw new System.InvalidOperationException();
			if (SampleRate > 0)
				if (MPEGLayer == 1)
					return ((12 * BitRate / SampleRate) + (Padding?1:0)) * 4;
				else
				{
					int SlotsPerFrame = (MPEGLayer == 3) && (MPEGLevel == 2)?72:144;
					return  ((SlotsPerFrame * BitRate) / SampleRate) + (Padding?1:0);
				}
			else
				return 0;
		}

		static int ExtractI4(byte[] buf, int off) {
			int x;
			// big endian extract

			x = buf[off+0];
			x <<= 8;
			x |= buf[off+1];
			x <<= 8;
			x |= buf[off+2];
			x <<= 8;
			x |= buf[off+3];

			return x;
		}
		
		private bool bit(int i, int bit)
		{
			return ((i >> bit) & 1) != 0;
		}

		private int vbrOffset { 				// determine offset of vbr header
			get {
				if (MPEGLevel == 1 ) {
					if (MPEGLayer != 1)
						return 32;
					else
						return 17;
				} else {
					if (MPEGLayer != 1)
						return 17;
					else
						return 9;
				}
			}
		}

		private bool isVbrFrame() {
			if (!headerValid) return false;
			int off = vbrOffset+4;

			if (Convert.ToChar(frameBuffer[off+0]) != 'X') return false;
			if (Convert.ToChar(frameBuffer[off+1]) != 'i') return false;
			if (Convert.ToChar(frameBuffer[off+2]) != 'n') return false;
			if (Convert.ToChar(frameBuffer[off+3]) != 'g') return false;
			return true;
		}

		private int vbrFlags {
			get {
				return ExtractI4(frameBuffer,vbrOffset+8);
			}
		}

		public bool IsVbrFrame {
			get {
				if (!headerValid)
					throw new System.InvalidOperationException();

				return vbrFrame;
			}
		}

		public int VbrTotalFrames() {
			if (!vbrFrame) return -1;
			if ((vbrFlags & VBR_FRAMES_FLAG) == 0) return -1;
			return ExtractI4(frameBuffer,vbrOffset+12);
		}
		
		private bool isSyncMark(int testHeader)
		{
			bool sync = false;
			
			sync = ((testHeader & 0xFFF00000) == 0xFFF00000);
			
			// filter out invalid sample rate, level, and bitrate
			//if (sync) sync = ((testHeader & 0x03)!=2);          // Bad Emphasis can't equal 2
			
			if (sync)
				sync = ((testHeader & 0x03) == 0);
			// Emphasis will realistically always=0
			
			if (sync)
				sync = (((testHeader >> 10) & 0x03) != 3);
			// Sample Rate can't be 3
			if (sync)
				sync = (((testHeader >> 17) & 0x03) == 1);
			// Layer can't equal 0
			if (sync)
				sync = (((testHeader >> 12) & 0x0F) != 15);
			// Bitrate can't equal 15
			if (sync)
				sync = (((testHeader >> 12) & 0x0F) != 0);
			// or 0
			
			if (strictSync && sync && prevHeader != 0)
				sync = ((bit(testHeader, 19) == bit(prevHeader, 19)));
			if (strictSync && sync && prevHeader != 0)
				sync = (((testHeader >> 10) & 0x03) == ((prevHeader >> 10) & 0x03));
			if (strictSync && sync && prevHeader != 0)
				sync = (((testHeader >> 17) & 0x03) == ((prevHeader >> 17) & 0x03));
			if (strictSync && sync && prevHeader != 0)
				sync = (((testHeader >> 17) & 0x03) == ((prevHeader >> 17) & 0x03));
			if (strictSync && sync && prevHeader != 0)
				sync = (((testHeader >> 6) & 0x02) == ((prevHeader >> 6) & 0x02));
			
			if (strictSync && sync)
				prevHeader = testHeader;
			return sync;
		}
		
		// Lookup Tables
		
		private static int[][][] bitrates = {new int[][]{new int[]{0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0}, new int[]{0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0}, new int[]{0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0}}, new int[][]{new int[]{0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0}, new int[]{0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}, new int[]{0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}}};
	}
}